package com.fand.utils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;

import com.google.common.base.Objects;
import com.google.common.collect.Lists;

public class BeanUtils {
	/**
	 * 比较两个对象,得到属性值不相等的属性名称集合
	 * 
	 * @param newObject
	 *            新对象
	 * 
	 * @param oldObject
	 *            旧对象
	 * 
	 * @param useProperties
	 *            使用属性集合
	 * 
	 * @return 修改属性集合
	 */
	public static String[] differProperties(final Object newObject, final Object oldObject,
			final String... useProperties) {
		return BeanUtils.differProperties(newObject, oldObject, null, useProperties);
	}

	/**
	 * 比较两个对象,得到属性值不相等的属性名称集合
	 * 
	 * @param newObject
	 *            新对象
	 * 
	 * @param oldObject
	 *            旧对象
	 * 
	 * @param editable
	 *            对象类型，控制比较属性的范围
	 * 
	 * @param useProperties
	 *            使用属性集合
	 * 
	 * @return 修改属性集合
	 * 
	 * @throws BeansException
	 */
	public static String[] differProperties(final Object newObject, final Object oldObject, Class<?> editable,
			final String... useProperties) throws BeansException {
		Assert.notNull(newObject, "newObject must not be null");

		Assert.notNull(oldObject, "oldObject must not be null");

		Class<?> actualEditable = oldObject.getClass();
		if (editable != null) {
			if (!editable.isInstance(oldObject)) {
				throw new IllegalArgumentException("oldObject class [" + oldObject.getClass().getName()
						+ "] not assignable to Editable class [" + editable.getName() + "]");
			}

			actualEditable = editable;
		}

		final PropertyDescriptor[] oldPds = org.springframework.beans.BeanUtils
				.getPropertyDescriptors(actualEditable);

		final List<String> usePropertieList = Lists.newArrayList();

		for (final PropertyDescriptor oldPd : oldPds) {
			if (oldPd.getReadMethod() == null) {
				continue;
			}

			if ((useProperties != null) && (!ArrayUtils.contains(useProperties, oldPd.getName()))) {
				continue;
			}

			final PropertyDescriptor newPd = org.springframework.beans.BeanUtils.getPropertyDescriptor(
					newObject.getClass(), oldPd.getName());

			if (newPd == null) {
				continue;
			}

			if (newPd.getReadMethod() == null) {
				continue;
			}

			try {
				final Method newReadMethod = newPd.getReadMethod();

				if (!Modifier.isPublic(newReadMethod.getDeclaringClass().getModifiers())) {
					newReadMethod.setAccessible(true);
				}

				final Object newValue = newReadMethod.invoke(newObject);

				final Method oldReadMethod = oldPd.getReadMethod();

				if (!Modifier.isPublic(oldReadMethod.getDeclaringClass().getModifiers())) {
					oldReadMethod.setAccessible(true);
				}

				final Object oldValue = oldReadMethod.invoke(oldObject);

				if (!Objects.equal(newValue, oldValue)) {
					usePropertieList.add(oldPd.getName());
				}
			} catch (final Throwable ex) {
				throw new FatalBeanException("Could equal properties from new to old", ex);
			}
		}

		return usePropertieList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
	}

	/**
	 * 复制属性
	 * 
	 * @param source
	 *            源对象
	 * @param target
	 *            目标对象
	 * @param includeProperties
	 *            包含属性
	 */
	public static void copyProperties(Object source, Object target, String... includeProperties) {
		copyProperties(source, target, null, includeProperties);
	}

	/**
	 * 复制属性
	 * 
	 * @param source
	 *            源对象
	 * @param target
	 *            目标对象
	 * @param editable
	 *            对象类型，控制复制属性的范围
	 * @param includeProperties
	 *            包含属性
	 * @throws BeansException
	 */
	public static void copyProperties(Object source, Object target, Class<?> editable, String... includeProperties)
			throws BeansException {
		Assert.notNull(source, "Source must not be null");

		Assert.notNull(target, "Target must not be null");

		Class<?> actualEditable = target.getClass();

		if (editable != null) {
			if (!editable.isInstance(target)) {
				throw new IllegalArgumentException("Target class [" + target.getClass().getName()
						+ "] not assignable to Editable class [" + editable.getName() + "]");
			}

			actualEditable = editable;
		}

		PropertyDescriptor[] targetPds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);

		for (PropertyDescriptor targetPd : targetPds) {
			if (targetPd.getWriteMethod() != null
					&& (includeProperties == null || (ArrayUtils.contains(includeProperties, targetPd.getName())))) {
				PropertyDescriptor sourcePd = org.springframework.beans.BeanUtils.getPropertyDescriptor(
						source.getClass(), targetPd.getName());
				if (sourcePd != null && sourcePd.getReadMethod() != null) {
					try {
						Method readMethod = sourcePd.getReadMethod();
						if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
							readMethod.setAccessible(true);
						}
						Object value = readMethod.invoke(source);
						Method writeMethod = targetPd.getWriteMethod();
						if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
							writeMethod.setAccessible(true);
						}
						writeMethod.invoke(target, value);
					} catch (Throwable ex) {
						throw new FatalBeanException("Could not copy properties from source to target", ex);
					}
				}
			}
		}
	}
}
